Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix for #18161 AsyncHttpServer Issue Too many open files. #18198

Closed
wants to merge 4 commits into from

Conversation

mrhdias
Copy link
Contributor

@mrhdias mrhdias commented Jun 6, 2021

I've been checking the code in asynchttpserver file and found that in the processClient function the connection is never closed after the "while" loop. So even if the keepalive header is off the number of open files is always increasing. With the keepalive off and if the connection is closed after the "while" loop the server never dies.

If the header keepalive is on and if it exceeds the pre-defined number of file descriptors it automatically closes the connection! I also added a timeout for keepalive connections.

I've been checking the code in asynchttpserver file and found that in the processClient function the connection is never closed after the "while" loop. So even if the keepalive header is off the number of open files is always increasing. With the keepalive off and if the connection is closed after the "while" loop the server never dies.

If the header keepalive is on and if it exceeds the pre-defined number of file descriptors it automatically closes the connection! I also added a timeout for keepalive connections.
Copy link
Contributor

@dom96 dom96 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a good patch when it comes to keep-alive timeout (and it might be good to keep this feature in its own PR and create a new one for the issue you're trying to fix). But you shouldn't just stop client processing when number of active descriptors is too high, that will just lead to more confusion.

You are fixing the keep-alive:off case, right? So why not check for this header and only process one request in that case?

lib/pure/asynchttpserver.nim Outdated Show resolved Hide resolved
mrhdias added 2 commits June 6, 2021 16:59
The default value for keepalive timeout is 3600 seconds.
…ons.

Disables the keepalive if the active file descriptors exceeds the maxFDs value or if the maximum keepalive timeout is exceeded. In this case, the new connections are closed until the value of the file descriptors drops back down to the maxFDs.
@mrhdias mrhdias requested a review from dom96 June 6, 2021 16:56
Comment on lines +314 to +315
if (fds > server.maxFDs) or ((now() - startTimeout).inSeconds > server.keepaliveTimeout):
request.mget().disableKeepalive = true
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, you shouldn't do this at all. You should check the headers and see whether keep-alive has been turned off.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@dom96 can you help me with the code? I want to close the connection when the number of file descriptors is high. Even if the value of keepalive is on I want to close the connection anyway. It is temporary until the number of files descriptors goes down, when the value goes down the connection can remain open. Please help me to solve the issue!

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mrhdias This isn't what you should do though! Don't close connections when the number of FDs is high, this will just lead to more problems.

@mrhdias
Copy link
Contributor Author

mrhdias commented Jun 6, 2021

With the solution I proposed the function shouldAcceptRequest is not necessary.

proc serve*(server: AsyncHttpServer, port: Port,
            callback: proc (request: Request): Future[void] {.closure, gcsafe.},
            address = "";
            assumedDescriptorsPerRequest = -1) {.async.} =

  listen server, port, address
  while true:
    #[ This part can be removed and replaced with the one below.
    if shouldAcceptRequest(server, assumedDescriptorsPerRequest):
      var (address, client) = await server.socket.acceptAddr()
      asyncCheck processClient(server, client, address, callback)
    else:
      poll()
    #echo(f.isNil)
    #echo(f.repr)
    ]#
    var (address, client) = await server.socket.acceptAddr()
    asyncCheck processClient(server, client, address, callback)

@dom96
Copy link
Contributor

dom96 commented Jun 6, 2021

So even if the keepalive header is off the number of open files is always increasing. With the keepalive off and if the connection is closed after the "while" loop the server never dies.

You are fixing the case when "keep-alive" is off. So please focus on that. We can deal with shouldAcceptRequest later.

@mrhdias mrhdias requested a review from dom96 June 6, 2021 17:30
@mrhdias
Copy link
Contributor Author

mrhdias commented Jun 6, 2021

So even if the keepalive header is off the number of open files is always increasing. With the keepalive off and if the connection is closed after the "while" loop the server never dies.

You are fixing the case when "keep-alive" is off. So please focus on that. We can deal with shouldAcceptRequest later.

The server does not die when the "keep-alive" is off. Dies when is on. If the value of header "connection" == "close" the connection is closed, if it is "keep-alive" it remains open and the number of file descriptors increases and the server die.

@timotheecour
Copy link
Member

@mrhdias this PR targets wrong branch; it should target devel; you don't need to open a new PR for that, you can change the target branch on same PR (read github docs before doing so)

@mrhdias mrhdias changed the base branch from version-1-4 to devel June 6, 2021 18:24
@mrhdias
Copy link
Contributor Author

mrhdias commented Jun 6, 2021

@mrhdias this PR targets wrong branch; it should target devel; you don't need to open a new PR for that, you can change the target branch on same PR (read github docs before doing so)

Thank you @timotheecour I already did it.

@timotheecour
Copy link
Member

timotheecour commented Jun 6, 2021

Thank you @timotheecour I already did it.

incorrectly though... since conflict files; that's why i suggested to read the github docs prior to doing this. you should:

  • change back target branch to something else (eg 1.4)
  • update your devel branch with git pull --ff-only
  • push it
  • rebase your PR branch against devel
  • change back this PR's target branch back to devel

it should work
(EDIT: see timotheecour#534 (comment))

@mrhdias mrhdias changed the base branch from devel to version-1-4 June 6, 2021 18:49
@mrhdias
Copy link
Contributor Author

mrhdias commented Jun 6, 2021

Thank you @timotheecour I already did it.

incorrectly though... since conflict files; that's why i suggested to read the github docs prior to doing this. you should:

* change back target branch to something else (eg 1.4)

* update your devel branch with git pull --ff-only

* push it

* rebase your PR branch against devel

* change back this PR's target branch back to devel

it should work

I will create another PR. There are too many conflicts and comments!

@dom96
Copy link
Contributor

dom96 commented Jun 6, 2021

The server does not die when the "keep-alive" is off. Dies when is on. If the value of header "connection" == "close" the connection is closed, if it is "keep-alive" it remains open and the number of file descriptors increases and the server die.

Then there is no problem? When you have keep-alive on and your server receives n connections then it should try to accept those connections.

Right now this is solved by shouldAcceptRequest so I don't see why that isn't working for you. But that's not all that important, the way asynchttpserver should work is like so:

  • call accept
  • if accept fails and it's because of max fd limit, log it and ignore.
  • Keep connection alive until timeout (the Keep-Alive header actually is used to specify how long connections should be kept alive for, we should have a min timeout and possibly support this header, but that can be done in yet another PR)
  • if Connection: Close is specified then connection should be closed instead of kept alive (but we already do this here: https://github.com/nim-lang/Nim/blob/devel/lib/pure/asynchttpserver.nim#L338!)

So what you (or someone else) can do is create two PRs:

  • implement keep-alive timeout
  • get rid of the shouldAcceptRequest and instead detect errors coming from accept, log those when they're about max fds being reached

@dom96 dom96 closed this Jun 6, 2021
@mrhdias
Copy link
Contributor Author

mrhdias commented Jun 6, 2021

The server does not die when the "keep-alive" is off. Dies when is on. If the value of header "connection" == "close" the connection is closed, if it is "keep-alive" it remains open and the number of file descriptors increases and the server die.

Then there is no problem? When you have keep-alive on and your server receives n connections then it should try to accept those connections.

The server accepts all connections, but temporarily disables the connection "keep-alive", It's the same as having the the "connection" equal to "close" in header.

Right now this is solved by shouldAcceptRequest so I don't see why that isn't working for you. But that's not all that important, the way asynchttpserver should work is like so:

The shouldAcceptRequest don't solve the issue because I took a look at the asyncdispatch file and I think the activeDescriptors function doesn't read the real number of open file descriptors!
timotheecour#750

* call accept

* if accept fails and it's because of max fd limit, log it and ignore.

* Keep connection alive until timeout (the `Keep-Alive` header actually is used to specify how long connections should be kept alive for, we should have a min timeout and possibly support this header, but that can be done in yet another PR)

* if `Connection: Close` is specified then connection should be closed instead of kept alive (but we already do this here: https://github.com/nim-lang/Nim/blob/devel/lib/pure/asynchttpserver.nim#L338!)

So what you (or someone else) can do is create two PRs:

* implement keep-alive timeout

* get rid of the `shouldAcceptRequest` and instead detect errors coming from `accept`, log those when they're about max fds being reached

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants